Skip to content

perf: optimize binary scanning with streaming and concurrent chunk processing - #96

Merged
MarshallOfSound merged 3 commits into
mainfrom
claude/optimize-fuse-flipping-tDgFy
Mar 27, 2026
Merged

perf: optimize binary scanning with streaming and concurrent chunk processing#96
MarshallOfSound merged 3 commits into
mainfrom
claude/optimize-fuse-flipping-tDgFy

Conversation

@MarshallOfSound

@MarshallOfSound MarshallOfSound commented Mar 24, 2026

Copy link
Copy Markdown
Member

Summary

Makes flipFuses and getCurrentFuseWire roughly 10–15× faster on typical Electron binaries, while capping peak memory at ~8 MB regardless of binary size.

Previously these functions loaded the entire Electron binary (100–300 MB) into a single buffer, scanned it, and — in the case of flipFuses — wrote the whole file back to disk even though only a handful of bytes change.

Benchmark results

Measured on synthetic binaries with embedded fuse wires (median of multiple runs):

Operation Binary Before After Speedup
flipFuses 50 MB 120 ms 14 ms 8.6×
flipFuses 150 MB 351 ms 35 ms 10×
flipFuses 300 MB (universal) 831 ms 58 ms 14×
getCurrentFuseWire 150 MB 160 ms 14 ms 11×
getCurrentFuseWire 300 MB 435 ms 28 ms 15×

Memory: peak usage drops from ~binary-size to a fixed ~8 MB.

How

  • Chunked scan: findSentinels() reads the file in 4 MB chunks instead of all at once. Each chunk overreads SENTINEL.length - 1 bytes into its neighbour so a sentinel straddling a boundary is still detected.
  • I/O ↔ CPU overlap: two concurrent workers process chunks so Buffer.indexOf on one chunk runs while the next chunk is being read from disk. Benchmarking showed concurrency=2 is the sweet spot — more workers don't help, and 32 MB chunks are actually slower than 4 MB due to allocation overhead and cache locality.
  • Positional writes: flipFuses now writes only the modified fuse wire bytes via handle.write(..., position) instead of rewriting the entire file.
  • Early exit: getCurrentFuseWire stops scanning as soon as the first sentinel is found.

Other changes

  • Added a guard that rejects binaries containing more than 2 sentinels, since only single-arch (1) and universal macOS (2) layouts are supported.
  • Extracted a readFuseWire() helper that coalesces the header + wire read into a single syscall and removes duplication between setFuseWire and getCurrentFuseWire.
  • File handles are closed in finally blocks for proper cleanup on error.

The public API is unchanged.

claude added 2 commits March 24, 2026 05:15
Previously flipFuses and getCurrentFuseWire loaded the entire Electron
binary into memory, scanned it, and (for flipFuses) wrote the whole file
back to disk even though only a handful of bytes change.

This switches to a chunked scan that holds ~8 MB in memory regardless of
binary size, runs two workers concurrently so indexOf overlaps with disk
reads, and uses positional writes to touch only the modified fuse bytes.

On synthetic binaries this yields roughly:
  flipFuses         150 MB:  ~350ms -> ~35ms
  flipFuses         300 MB:  ~830ms -> ~58ms
  getCurrentFuseWire 300 MB: ~435ms -> ~28ms
Only 1 sentinel (single-arch) or 2 sentinels (universal macOS) are valid.
Finding more indicates a corrupted or unsupported binary, so fail loudly
rather than silently patching an unknown layout.
@MarshallOfSound
MarshallOfSound requested a review from a team as a code owner March 24, 2026 05:16
@MarshallOfSound MarshallOfSound changed the title Optimize binary scanning with streaming and concurrent chunk processing perf: optimize binary scanning with streaming and concurrent chunk processing Mar 24, 2026
Restores the atomicity guarantee of the original implementation: if
validation fails for any sentinel (version mismatch, strictlyRequireAllFuses),
the binary is left completely unmodified.

Previously, with per-sentinel writes inside the validation loop, a
universal binary could end up with one arch slice modified and the other
untouched if the second slice failed validation.
@MarshallOfSound
MarshallOfSound merged commit 0b8ac07 into main Mar 27, 2026
6 checks passed
@MarshallOfSound
MarshallOfSound deleted the claude/optimize-fuse-flipping-tDgFy branch March 27, 2026 02:29
@electron-npm-package-publisher

Copy link
Copy Markdown

🎉 This PR is included in version 2.1.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

MarshallOfSound added a commit to electron/packager that referenced this pull request Mar 31, 2026
Previously setIntegrityDigest loaded the entire Electron Framework
binary (150-500 MB) into memory, scanned it, modified 34 bytes, and
wrote the whole thing back. Now it scans in 4 MB chunks with two
concurrent workers (I/O overlaps Buffer.indexOf CPU) and patches only
the 34-byte digest slot(s) via handle.write at the found position(s).
Each chunk overreads sentinel.length-1 bytes so a sentinel straddling
a boundary is still detected. Peak memory drops from ~binary-size to
~8 MB. Same approach as electron/fuses#96, which benched 10-15x faster
on 150-300 MB binaries.

Adds two synthetic-binary tests: one plants the sentinel across a 4 MB
boundary (verified load-bearing via mutation), one plants a sentinel in
each of two chunks to cover the universal-binary multi-write path. Both
skip packager() and run in ~10ms.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants